import { fileURLToPath, URL } from 'node:url';

import vitePluginPrefetch from '@erdai/vite-plugin-prefetch';
import terser from '@rollup/plugin-terser';
import react from '@vitejs/plugin-react';
import externalGlobals from 'rollup-plugin-external-globals';
import { visualizer } from 'rollup-plugin-visualizer';
import AutoImport from 'unplugin-auto-import/vite';
import type { ConfigEnv, UserConfig } from 'vite';
import { defineConfig, loadEnv } from 'vite';
import checker from 'vite-plugin-checker';
import viteCompression from 'vite-plugin-compression';
import { createHtmlPlugin } from 'vite-plugin-html';
import { viteMockServe } from 'vite-plugin-mock';

/**
 * 将一个相对于当前模块文件的路径（relativePath）转换为一个绝对路径。
 *
 * @param relativePath 相对路径
 * @returns 绝对路径
 */
function resolvePath(relativePath: string): string {
    // 使用fileURLToPath和new URL结合import.meta.url更推荐，在ES模块环境中推荐使用
    return fileURLToPath(new URL(relativePath, import.meta.url));
}

export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
    // 获取项目根目录(index.html所在的目录)
    const root = process.cwd();
    // 读取环境变量配置文件，获取环境变量
    const env = loadEnv(mode, root);

    // 可在 .env.* 中配置这些值
    const VITE_APP_USE_MOCK = env.VITE_APP_USE_MOCK ?? 'false';
    const VITE_APP_TITLE = env.VITE_APP_TITLE ?? 'admin后台管理系统模板';
    const VITE_ANALYZE = env.VITE_ANALYZE ?? 'false';
    const VITE_ANALYZE_RAW = env.VITE_ANALYZE_RAW ?? 'false';
    const VITE_USE_TERSER = env.VITE_USE_TERSER ?? 'false';
    const VITE_APP_API_BASEURL = env.VITE_APP_API_BASEURL ?? '/api';
    const VITE_APP_MOCK_API_BASEURL = env.VITE_APP_MOCK_API_BASEURL ?? '/mock/api';
    const VITE_HOST = env.VITE_HOST ?? '0.0.0.0';
    const VITE_PORT = Number(env.VITE_PORT ?? 9000);
    const VITE_STRICT_PORT = (env.VITE_STRICT_PORT ?? 'true') === 'true';

    const isProd = mode === 'production';
    const enableAnalyze = VITE_ANALYZE === 'true';
    const enableAnalyzeRaw = VITE_ANALYZE_RAW === 'true';

    return {
        root,
        // 开发或生产环境服务的公共基础路径。
        base: '/',
        // 为静态资源服务的文件夹，默认为'public'
        publicDir: 'public',
        // 缓存目录，加快冷启动
        cacheDir: 'node_modules/.vite_cache',
        resolve: {
            // 配置文件系统路径别名
            alias: {
                '@': resolvePath('./src'),
                '#': resolvePath('./types')
            }
        },
        css: {
            preprocessorOptions: {
                // 处理less:注意，如果添加是实际的样式而不仅仅是变量，那这些样式在最终的产物中会重复
                less: {
                    // 全局 less 变量/混合
                    additionalData: '@import url("@/styles/variable.less");'
                }
            }
        },
        /**
         * 预构建优化：缩短 dev 冷启动与首次 HMR 时间，并减少因 CJS/ESM 导出差异导致的运行时错误提示。
         * 如出现错误，删除node_modules/.vite_cache重新启动
         */
        optimizeDeps: {
            // 根据 package.json 中实际依赖，预构建常用且体积较大的包以加快 dev 启动
            include: [
                'react',
                'react-dom',
                'react-router-dom',
                'axios',
                'ahooks',
                'antd',
                '@ant-design/icons',
                'echarts',
                'zustand'
            ],
            // 如果有需要排除的包（例如用于 SSR 的特殊包或本地链接包），在此列出
            exclude: []
        },
        // 插件配置
        plugins: [
            react(),
            AutoImport({
                // 自动导入 react-router、react-router-dom相关函数
                imports: ['react', 'react-router-dom', 'ahooks'],
                // 对于自动引入的框架，开启eslint支持, 自动生成.eslintrc-auto-import.json文件，需要在.eslintrc.js中extends配置项中引入
                eslintrc: {
                    enabled: true,
                    filepath: './.eslintrc-auto-import.json'
                },
                // 配置生成.d.ts的生成目录
                dts: resolvePath('./types/auto-imports.d.ts')
            }),
            // 开发运行时：实时开启 (类型 + ESLint) 检查
            checker({
                typescript: {
                    // typescript 检查（使用项目 tsconfig，减少与 IDE/tsserver 的差异）
                    tsconfigPath: resolvePath('./tsconfig.json')
                },
                // 在开发时执行与 package.json 对齐的 eslint 检查（可选）
                eslint: {
                    lintCommand: 'eslint --cache --max-warnings 0 "{src,mock}/**/*.{ts,tsx,js,jsx}"'
                },
                // overlay 初始不自动打开，避免频繁打断开发（按需打开）
                overlay: {
                    initialIsOpen: false
                }
            }),
            // 自定义生成html插件，用于注入外部脚本标签等操作
            createHtmlPlugin({
                // 注入外部脚本标签,在index.html模板文件中需配置ejs语法，例如：<%= jsPDFScript %>
                inject: {
                    data: {
                        title: VITE_APP_TITLE,
                        cdnScripts: `
                            <!-- jsPDF -->
                            <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
                        `
                    }
                }
            }),
            // mock 在非生产并且 env 开启时启用
            viteMockServe({
                enable: VITE_APP_USE_MOCK === 'true' && !isProd,
                mockPath: 'mock'
            }),
            isProd &&
                viteCompression({
                    // 体积大于20kb的文件会被压缩
                    threshold: 1024 * 20,
                    // 压缩文件后缀，默认为 .gz
                    ext: '.gz',
                    // 压缩算法
                    algorithm: 'gzip'
                }),
            isProd &&
                viteCompression({
                    // 体积大于20kb的文件会被压缩
                    threshold: 1024 * 20,
                    // 压缩文件后缀，默认为 .gz
                    ext: '.br',
                    // 压缩算法
                    algorithm: 'brotliCompress'
                }),
            // 按需开启打包分析:可视化 html
            enableAnalyze &&
                visualizer({
                    // 生成的分析文件名称
                    filename: 'stats.html',
                    // 是否在生成后自动打开浏览器
                    open: true,
                    // 可视化模板，可选 'sunburst', 'network', 'treemap', 'raw-data'
                    template: 'treemap',
                    // 是否显示 gzip 压缩后的大小
                    gzipSize: true,
                    // 是否显示 brotli 压缩后的大小
                    brotliSize: true,
                    // 确保能读取 sourcemap 信息
                    sourcemap: true
                }),
            // 预取
            vitePluginPrefetch({
                enable: isProd,
                // 指定user页面在首屏时预取
                includePages: ['src/views/system/user/index.tsx'],
                // 预取文件大小阈值
                preloadThresholdKB: 6
            })
        ].filter(Boolean),
        // 开发环境配置
        server: {
            host: VITE_HOST,
            port: VITE_PORT,
            strictPort: VITE_STRICT_PORT,
            open: true,
            cors: true,
            proxy: {
                // 以/api开头发送的请求都会被转发到 后端真实地址
                [VITE_APP_API_BASEURL]: {
                    // 开发时，替换为对应后端的IP地址
                    target: 'https://m1.apifoxmock.com/m1/5961994-5650132-default',
                    // 改变 Host Header,允许跨域请求携带cookies
                    changeOrigin: true,
                    // 发起请求时将 '/api' 替换为 ''
                    rewrite: (path) => path.replace(new RegExp(`^${VITE_APP_API_BASEURL}`), '')
                },
                // 以/mock/api开头发送的请求都会被转发到 mock服务器地址
                [VITE_APP_MOCK_API_BASEURL]: {
                    // mock server启动在项目同端口下
                    target: 'http://localhost:9000',
                    // 改变 Host Header,允许跨域请求携带cookies
                    changeOrigin: true
                }
            }
        },
        // 生产环境配置
        build: {
            // 开发：inline，生产：hidden（便于上报但不暴露引用）
            sourcemap: isProd ? 'hidden' : 'inline',
            // 生成源文件和构建文件的映射关系文件
            manifest: true,
            // 推荐：针对现代浏览器运行
            target: 'es2018',
            // 默认使用 esbuild 提速，必要时通过 VITE_USE_TERSER 切换到 terser
            minify: VITE_USE_TERSER === 'true' ? 'terser' : 'esbuild',
            // 小于此阈值的静态资源（图片、字体等）将内联为 base64 编码，默认4kb
            assetsInlineLimit: 4 * 1024,
            // 启用按 chunk 拆分 CSS
            cssCodeSplit: true,
            // 在每次构建前清空输出目录（outDir，通常是 dist），删除旧文件。
            emptyOutDir: true,
            // 警告单个文件体积超过600kb时，会显示警告信息
            chunkSizeWarningLimit: 600,
            // terser压缩配置
            terserOptions:
                VITE_USE_TERSER === 'true'
                    ? {
                          compress: {
                              // 删除console.log等调试语句
                              drop_console: true,
                              // 删除debugger语句
                              drop_debugger: true,
                              // 删除无用代码（如未使用的变量）
                              dead_code: true,
                              // 提升变量声明
                              hoist_vars: true,
                              // 消除内联函数和循环内的重复计算
                              inline: 2,
                              // 移除不必要的圆括号
                              collapse_vars: true
                          },
                          output: {
                              // 最大化压缩数字与字符串字面量
                              ascii_only: false,
                              // 移除所有的注释，包括版权信息
                              comments: false,
                              // 控制是否保留引号，true表示尽可能转换为单引号
                              quote_style: 1
                          }
                      }
                    : undefined,
            // rollup打包配置
            rollupOptions: {
                // 将大型库外部化，使用 CDN 注入到 index.html
                external: ['jsPDF'],
                // 插件配置
                plugins: [
                    /*
                       1、需要排除的模块，不打入到最终的bundle中，利用外链引入, 例如jspdf等工具库，可以减少打包体积
                       2、build.rollupOptions.external: ['jsPDF']要相应配置
                       3、types/global.d.ts中声明全局变量类型 jsPDF
                       4、使用时，jspdf为全局变量，可直接使用，或者window.jspdf使用
                    */
                    externalGlobals({
                        jsPDF: 'jsPDF'
                        // ...按需增加需要外链的工具库
                    }),
                    ...(VITE_USE_TERSER === 'true' ? [terser()] : []),
                    // 按需开启打包分析:原始数据 json
                    enableAnalyzeRaw &&
                        visualizer({
                            // 生成的分析文件名称
                            filename: 'stats.raw.json',
                            // 是否在生成后自动打开浏览器
                            open: false,
                            // 可视化模板，可选 'sunburst', 'network', 'treemap', 'raw-data'
                            template: 'raw-data',
                            // 是否显示 gzip 压缩后的大小
                            gzipSize: true,
                            // 是否显示 brotli 压缩后的大小
                            brotliSize: true,
                            // 确保能读取 sourcemap 信息
                            sourcemap: true
                        })
                ],
                // 输出配置
                output: {
                    /**
                     * 业务打包合并，将小于20kb的模块合并到一起，减少请求数量(注意：只有在不改变加载任何条目时执行的副作用的情况下，才会执行合并)
                     */
                    // experimentalMinChunkSize: 20 * 1024,
                    /**
                     * 1.业务代码通过React.lazy加载，此处不做手动分割
                     * 2.非业务代码手动分割，此处将第三方库单独打包，便于缓存
                     * @param id 模块的绝对或相对路径(/project/src/views/system/user/index.tsx 或 /project/node_modules/antd/lib/button.js）)
                     * @returns 该模块应放入的 chunk 名
                     */
                    // ...existing code...
                    manualChunks(id: string) {
                        // 只处理来自 node_modules 的模块，其他交由 rollup 默认策略
                        if (!id || id.indexOf('node_modules') === -1) return;

                        /**
                         * getPkgName
                         * 目的：从模块路径中稳健地提取“包名”，返回格式为：
                         *   - 普通包：name（例如 axios）
                         *   - 带 scope 的包：@scope/name（例如 @ant-design/icons）
                         *
                         * 要处理的常见路径示例（实际打包中会遇到）：
                         *  1) /.../node_modules/axios/index.js                 -> axios
                         *  2) /.../node_modules/@scope/pkg/lib/foo.js          -> @scope/pkg
                         *  3) /.../.pnpm/pkg@1.2.3/node_modules/pkg/xxx.js      -> pkg
                         *  4) /.../.pnpm/@scope+pkg@1.2.3/node_modules/@scope/pkg/yyy.js -> @scope/pkg
                         *
                         * 说明：pnpm 会把依赖安装在 .pnpm 目录下并有嵌套 node_modules，需要优先处理。
                         */
                        function getPkgName(id: string): string {
                            // 1) 优先匹配 pnpm 的嵌套结构：.../node_modules/.pnpm/<pkg>@ver/node_modules/<pkg>/...
                            //    捕获组返回形如 "@scope/name" 或 "name"
                            const pnpmMatch = id.match(
                                /node_modules\/\.pnpm\/(?:@?[^/]+)\/node_modules\/((?:@[^/]+\/[^/]+)|[^/]+)/
                            );
                            if (pnpmMatch && pnpmMatch[1]) {
                                // pnpmMatch[1] 已经是包名（可能还包含多余片段，但通常直接返回可靠）
                                // 为保险起见，再用下面的通用正则截取出 @scope/name 或 name
                                const m = pnpmMatch[1].match(/^@[^/]+\/[^/]+|^[^/]+/);
                                return m ? m[0] : pnpmMatch[1];
                            }

                            // 2) 非 pnpm（常规 node_modules）：
                            //    取最后一个 node_modules 之后的路径片段，防止多层 node_modules 嵌套影响解析
                            const parts = id.split('node_modules/').filter(Boolean);
                            const last = parts[parts.length - 1] || '';

                            // 匹配 scoped 包 (@scope/name) 或普通包名 (name)
                            const scopedOrName = last.match(/^@[^/]+\/[^/]+|^[^/]+/);
                            return scopedOrName ? scopedOrName[0] : last.split('/')[0];
                        }

                        // 提取包名，返回示例：'react'、'@ant-design/icons'、'rc-table'
                        const pkg = getPkgName(id);
                        // basePkg 保持与 pkg 相同（便于后续扩展或进一步规范化）
                        const basePkg = pkg;

                        /**
                         * 分组策略（根据包名把常见第三方库拆到固定的 vendor chunk）
                         * 说明：
                         *  - 使用严格的包名比较（basePkg === 'name' 或 basePkg === '@scope/name'）
                         *  - 对于某些包会有子路径（例如 'react/jsx-runtime'），因此对部分包使用 startsWith 检查（见注释）
                         *  - 对于需要长期缓存且不常变动的库，单独拆出能提高缓存命中率
                         *  - 对于体积很小或不频繁复用的工具库，建议交给 rollup 自动分割，避免请求过多
                         */

                        // React 相关（包含 react 和 react-dom）
                        if (basePkg === 'react' || basePkg === 'react-dom' || pkg.startsWith('react/')) {
                            return 'vendor-react';
                        }
                        // React Router
                        if (basePkg === 'react-router-dom' || pkg.startsWith('react-router-dom/')) {
                            return 'vendor-router';
                        }

                        // Ant Design：把 icons 单独拆出，antd 核心另为一组
                        if (basePkg === '@ant-design/icons' || pkg.startsWith('@ant-design/icons/')) {
                            return 'vendor-icons';
                        }
                        if (basePkg === 'antd' || basePkg.startsWith('@ant-design') || pkg.startsWith('antd/')) {
                            return 'vendor-antd';
                        }

                        // ECharts 与 zrender（zrender 为独立包，但通常与 echarts 同使用，合并到同一 chunk）
                        if (
                            basePkg === 'echarts' ||
                            basePkg === 'zrender' ||
                            pkg.startsWith('echarts/') ||
                            pkg.startsWith('zrender/')
                        ) {
                            return 'vendor-echarts';
                        }

                        // 常见状态管理 / 请求库
                        if (basePkg === 'zustand' || pkg.startsWith('zustand/')) return 'vendor-zustand';
                        if (basePkg === 'axios' || pkg.startsWith('axios/')) return 'vendor-axios';

                        // 优先名单：把列表内包按包名生成 vendor-<name>，便于按需缓存（例如 lodash、dayjs）
                        const safeName = basePkg.replace('@', '').replace('/', '_'); // @scope/name -> scope_name
                        const preferList = ['ahooks'];
                        if (preferList.includes(basePkg) || preferList.includes(safeName)) {
                            return `vendor-${safeName}`;
                        }

                        // 其余情况交由 rollup/esbuild 的自动分割策略处
                        return undefined;
                    },
                    /**
                     * 打包后的文件名配置，此处使用hash值作为文件名的一部分，便于缓存
                     * 设置前置文件夹，可利用nginx配置缓存策略
                     */
                    chunkFileNames: 'static/js/[name]-[hash].js',
                    entryFileNames: 'static/js/entry-[hash].js',
                    assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
                }
            },
            // 关闭计算压缩大小，可以略微提升打包速度
            reportCompressedSize: false
        }
    };
});
